﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using LibMinecraft.Model;
using LibMinecraft.Server;
using System.Reflection;
using System.Diagnostics;
using System.Threading;
using PartyCraftServer.Commands;
using LibMinecraft.Model.Packets;
using LibMinecraft.Model.Entities;
using PartyCraftServer.Plugins;
using System.Xml.Linq;
using System.Xml;
using PartyCraftServer.Web;

namespace PartyCraftServer
{
    public class PartyCraft
    {
        #region Properties

        public static readonly Version Version = new Version(0, 3, 0, 1);

        public static XmlSettings Settings { get; set; }
        public static MinecraftServer CurrentServer { get; set; }
        public static MultiplayerServer Server { get; set; }
        public static readonly DateTime StartTime = DateTime.Now;
        public static AutoResetEvent AsyncReset;
        // We don't want plugins installing other plugins
        internal static PluginManager PluginManager { get; set; }
        public static string SettingsDirectory { get; set; }
        public static WebService WebService { get; set; }
        public static List<ChatLog> ChatLog { get; set; }

        public static AsyncAction AsyncAction { get; set; }

        static double _speed;
        public static double Speed
        {
            get
            {
                return _speed;
            }
            private set
            {
                _speed = value;
            }
        }

        private static DateTime LastTick;
        private static DateTime NextReport;
        private static Thread AnalyticsThread;

        #endregion

        #region Events

        public static event EventHandler<LogEventArgs> OnLog;

        #endregion

        public static void InitializeSettings()
        {
            if (Settings == null)
                Settings = new XmlSettings();
            LoadDefaultSettings();
            Settings.Load(SettingsDirectory);
        }

        public static void Run()
        {
            if (Settings == null)
                Settings = new XmlSettings();
            LoadDefaultSettings();
            Settings.Load(SettingsDirectory);
            Settings.OnSettingChanged += new EventHandler<SettingChangedEventArgs>(Settings_OnSettingChanged);
            Settings.EnableSaving = true;

            PluginManager = new PluginManager();

            AsyncReset = new AutoResetEvent(false);

            AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);

            Log("Welcome to PartyCraft v" + Version.ToString(2) + "!");
            Log("The server is starting on " + DateTime.Now.ToString("d MMM yyy") + " at " + DateTime.Now.ToString("HH:mm:ss"));

            // Initialize server settings
            CurrentServer = new MinecraftServer();
            CurrentServer.Port = Settings.GetInt("server.port");
            CurrentServer.MotD = Settings["server.motd"];
            CurrentServer.MaxPlayers = Settings.GetInt("server.maxplayers");
            CurrentServer.OnlineMode = Boolean.Parse(Settings["server.onlinemode"]);

            Log("Starting server on port " + Settings["server.port"] + "...");

            Server = new MultiplayerServer(CurrentServer);
            Server.SaveFrequency = -1; // TEMP
            World w = new World();
            try
            {
                w.LoadFromBinary(Settings["server.worlds.save"]);
            }
            catch
            {
                w.SaveToBinary(Settings["server.worlds.save"]);
            }
            Server.AddWorld(w);

            ChatLog = new List<PartyCraftServer.ChatLog>();
            // Register event handlers
            Server.OnChat += new EventHandler<ChatEventArgs>(Server_OnChat);
            Server.OnPlayerJoin += new EventHandler<PlayerEventArgs>(Server_OnPlayerJoin);
            Server.OnPlayerLeave += new EventHandler<PlayerEventArgs>(Server_OnPlayerLeave);
            Server.OnTick += new EventHandler(Server_OnTick);

            // Set up timings
            LastTick = DateTime.Now;
            NextReport = DateTime.Now.AddHours(1);
            NextReport = NextReport.Subtract(new TimeSpan(0, NextReport.Minute, NextReport.Second));

            Server.Start();

            try
            {
                WebService = new Web.WebService();
                Log("Starting web server on port " + Settings["server.web.port"] + "...");
                WebService.Start();
                Log("Web server started successfully!");
            }
            catch
            {
                Log("ERROR: Unable to start web server");
                Log("You must run PartyCraft as Administrator (Windows) or root (Linux) to use the web server");
            }

            ThreadStart ts = new ThreadStart(AsyncService);
            Thread t = new Thread(ts);
            t.Start();

            if (Settings.GetBoolean("server.analytics.enabled"))
            {
                Log("Starting up analytics...");
                Analytics.Initialize();
                AnalyticsThread = new Thread(new ThreadStart(DoAnalytics));
                AnalyticsThread.Start();
            }

            Log("Server started successfully!");
        }

        private static void DoAnalytics()
        {
            while (Settings.GetBoolean("server.analytics.enabled"))
            {
                Analytics.TakeSnapshot();
                Thread.Sleep(Settings.GetInt("server.analytics.capturefrequency"));
            }
        }

        public static void SendToOps(string Chat)
        {
            foreach (RemoteClient c in Server.ConnectedClients
                .Where(p => p.Tags.ContainsKey("op") ? (bool)p.Tags["op"] : false))
            {
                c.PacketQueue.Enqueue(new ChatMessagePacket(Chat));
            }
        }

        public static bool IsOp(RemoteClient c)
        {
            return (bool)c.Tags["op"];
        }

        static void AsyncService()
        {
            while (true)
            {
                AsyncAction = AsyncAction.None;

                AsyncReset.WaitOne();
                AsyncReset.Reset();

                switch (AsyncAction)
                {
                    case AsyncAction.RestartServer:
                        DateTime start = DateTime.Now;
                        foreach (RemoteClient c in Server.ConnectedClients)
                            Server.KickPlayer(c.PlayerEntity.Name, "Restarting server");
                        while ((DateTime.Now - start).TotalSeconds < 5 && Server.ConnectedClients.Count != 0) ; // wait for all clients to disconnect, up to 5 seconds

                        Log("RESTARTING SERVER");
                        Server.Stop();
                        //foreach (World w in Server.Worlds)
                        //    w.Save();
                        // TODO

                        CurrentServer = new MinecraftServer();
                        CurrentServer.Port = Settings.GetInt("server.port");
                        CurrentServer.MotD = Settings["server.motd"];
                        CurrentServer.MaxPlayers = Settings.GetInt("server.maxplayers");
                        CurrentServer.OnlineMode = Boolean.Parse(Settings["server.onlinemode"]);

                        Server.Start();

                        Log("Server started successfully!");
                        break;
                    case AsyncAction.StopServer:
                        Log("Stopping server!");
                        Server.Stop();
                        foreach (World w in Server.Worlds)
                            w.Save();
                        break;
                }
            }
        }

        static void Server_OnTick(object sender, EventArgs e)
        {
            TimeSpan timeBetweenTicks = DateTime.Now - LastTick;
            Speed = timeBetweenTicks.TotalMilliseconds / 50d;

            if (NextReport < DateTime.Now)
            {
                Log("Hourly server report:");
                Log("Uptime: " + (DateTime.Now - StartTime).ToString());
                Log("Connected players: " + Server.ConnectedClients.Count + "/" + CurrentServer.MaxPlayers);
                Log("Current Speed: " + (int)(Speed * 100) + "%");

                NextReport = DateTime.Now.AddHours(1);
                NextReport = NextReport.Subtract(new TimeSpan(0, NextReport.Minute, NextReport.Second));
            }

            LastTick = DateTime.Now;
        }

        static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
        {
            try
            {
                Log("Error: " + e.ExceptionObject.GetType().Name + ": " + ((Exception)e.ExceptionObject).Message);
            }
            catch
            {
            }
        }

        private static void LoadDefaultSettings()
        {
            #region Messages
            Settings["server.messages.playerjoin"] = "§e{0} joined the game.";
            Settings["server.messages.playerleave"] = "§e{0} left the game.";
            Settings["server.messages.restart"] = "Server is restarting!";
            Settings["server.messages.portchange"] = "Server port is changing to {0}";
            Settings["server.messages.op"] = "§9You are now an operator!";
            Settings["server.messages.kick"] = "You have been kicked from this server.";
            #endregion

            Settings["server.port"] = "25565";
            Settings["server.motd"] = "PartyCraft v" + Version.ToString(2);
            Settings["server.maxplayers"] = "25";
            Settings["server.difficulty"] = "normal";
            Settings["server.gamemode"] = "creative";
            Settings["server.onlinemode"] = "true";
            Settings["chat.format"] = "<{0}> {1}";
            Settings["server.logfile"] = "server.log";
            Settings["server.worlds.save"] = "world";
            Settings["server.savefrequency"] = "100";
            Settings["server.analytics.enabled"] = "false";
            // Web
            Settings["server.web.enabled"] = "true";
            Settings["server.web.port"] = "80";

#if DEBUG
            Settings["server.debugenabled"] = "true";
            Settings["server.onlinemode"] = "false";
            Settings["server.analytics.enabled"] = "true";
            Settings["server.analytics.capturefrequency"] = "1000";
#endif
            if (!Directory.Exists("players"))
                Directory.CreateDirectory("players");
        }

        public static void Log(string Message)
        {
            try
            {
                if (Settings == null || Message == null || OnLog == null)
                    return;
                string prepend = "[" + DateTime.Now.ToString("HH:mm:ss") + "] ";
                if (Settings["server.debugenabled"] == "true")
                {
                    StackTrace trace = new StackTrace();
                    prepend += trace.GetFrame(1).GetMethod().DeclaringType.Name + "." + trace.GetFrame(1).GetMethod().Name + ": ";
                }
                OnLog(null, new LogEventArgs(prepend + Message));
            }
            catch { }
        }

        public static string[] GetLastFifteenMessages()
        {
            List<string> result = new List<string>();
            for (int i = ChatLog.Count - 15; i < ChatLog.Count; i++)
            {
                if (i >= 0)
                    result.Add(ChatLog[i].Message);
            }
            return result.ToArray();
        }

        #region Event Handlers

        static void Server_OnPlayerJoin(object sender, PlayerEventArgs e)
        {
            // Set properties on player
            e.Player.Tags["op"] = false;
            if (!File.Exists(Path.Combine(SettingsDirectory, "players", e.Player.PlayerEntity.Name + ".xml")))
            {
                XDocument d = new XDocument();
                d.Add(new XElement("partycraft"));
                XElement player = new XElement("player");
                XElement subPlayer = new XElement(e.Player.PlayerEntity.Name.ToLower());
                subPlayer.Add(new XElement("op", "false"));
                player.Add(subPlayer);
                d.Root.Add(player);

                d.Save(Path.Combine(SettingsDirectory, "players", e.Player.PlayerEntity.Name + ".xml"));

                Settings.LoadFile(Path.Combine(SettingsDirectory, "players", e.Player.PlayerEntity.Name + ".xml"));
            }
            else
                e.Player.Tags["op"] = Settings.GetBoolean("player." + e.Player.PlayerEntity.Name + ".op");
            Server.SendChat(string.Format(Settings["server.messages.playerjoin"], e.Player.PlayerEntity.Name));
            ChatLog.Add(new ChatLog(DateTime.Now, string.Format(Settings["server.messages.playerjoin"], e.Player.PlayerEntity.Name)));
            Log(string.Format(Settings["server.messages.playerjoin"], e.Player.PlayerEntity.Name));
        }

        static void Server_OnPlayerLeave(object sender, PlayerEventArgs e)
        {
            Server.SendChat(string.Format(Settings["server.messages.playerleave"], e.Player.PlayerEntity.Name));
            ChatLog.Add(new ChatLog(DateTime.Now, string.Format(Settings["server.messages.playerleave"], e.Player.PlayerEntity.Name)));
            Log(string.Format(Settings["server.messages.playerleave"], e.Player.PlayerEntity.Name));
        }

        static void Server_OnChat(object sender, ChatEventArgs e)
        {
            Log(e.RawText);
            e.Handled = true;
            if (e.Message.StartsWith("/"))
                Command.Execute(e.RemoteClient, e.Message);
            else
            {
                Server.SendChat(string.Format(Settings["chat.format"], e.UserName, e.Message));
                ChatLog.Add(new ChatLog(DateTime.Now, string.Format(Settings["chat.format"], e.UserName, e.Message)));
            }
        }

        static void Settings_OnSettingChanged(object sender, SettingChangedEventArgs e)
        {
            // Special case settings
            switch (e.Key)
            {
                case "server.motd":
                    CurrentServer.MotD = e.NewValue;
                    break;
                case "server.port":
                    lock (PartyCraft.Server.SendQueueLock)
                    {
                        foreach (RemoteClient c in Server.ConnectedClients)
                        {
                            PartyCraft.Server.KickPlayer(c.PlayerEntity.Name, string.Format(Settings["server.messages.portchange"], e.NewValue));
                        }
                    }
                    CurrentServer.Port = int.Parse(e.NewValue);
                    AsyncAction = AsyncAction.RestartServer;
                    AsyncReset.Set();
                    break;
                case "server.savefrequency":
                    Server.SaveFrequency = int.Parse(e.NewValue);
                    break;
                case "server.onlinemode":
                    CurrentServer.OnlineMode = bool.Parse(e.NewValue);
                    break;
                case "server.maxplayers":
                    CurrentServer.MaxPlayers = 100;
                    break;
            }
        }

        #endregion
    }
}
